package code.google.jcontainer.invoke;

import code.google.jcontainer.AbstractContainer;
import code.google.jcontainer.ComponentMeta;
import code.google.jcontainer.resolver.AnnotationResolverRepository;
import code.google.jcontainer.resolver.MethodResolveContext;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * Invocation contains all needed resources for method invocation
 *
 * @author <a href="mailto:jfox.young@gmail.com">Young Yang</a>
 */
public class Invocation {

    private Object component;
    private Method method;
    private Object[] parameters;
    private Object result;

    private Map<String, Object> userData = new HashMap<String, Object>(5);

    private AbstractContainer container;
    private ComponentMeta componentMeta = null;

    private MethodResolveContext methodResolveContext;

    public Invocation(AbstractContainer container, ComponentMeta componentMeta, Object component, Method method, Object[] parameters) {
        this.container = container;
        this.componentMeta = componentMeta;
        this.component = component;
        this.method = method;
        this.parameters = parameters;
    }

    public AnnotationResolverRepository getAnnotationResolveRepository(){
        return container.getAnnotationResolverRepository();
    }

    public AbstractContainer getContainer() {
        return container;
    }
    
    public Object getTargetComponent() {
        return component;
    }

    public Method getTargetMethod() {
        return method;
    }

    public Object[] getParameters() {
        return parameters;
    }

    public void setParameters(Object[] parameters) {
        this.parameters = parameters;
    }

    public void setResult(Object result) {
        this.result = result;
    }

    public Object getResult() {
        return result;
    }

    /**
     * invoke InvocationHandlers one by one
     */
    public final void invokeByChain(final Iterator<InvocationHandler> chain) throws Exception {
        if(chain.hasNext()) {
            InvocationHandler handler = chain.next();
            try {
                handler.chainInvoke(this);
                invokeByChain(chain);
            }
            catch (Exception e) {
                handler.onCaughtException(this, e);
            }
            finally {
                handler.chainReturn(this);
            }
        }
    }

    public void onCaughtException(Exception e) {
        throw  new InvocationRuntimeException(e);
    }

    public void setUserData(String key, Object value) {
        userData.put(key, value);
    }

    public Object getUserData(String key) {
        return userData.get(key);
    }

    public Annotation[] getMethodAnnotations() {
        return componentMeta.getMethodAnnotations().get(method);
    }

    public MethodResolveContext getMethodAnnotationResolveContext() {
        return methodResolveContext;
    }

    public void setMethodAnnotationResolveContext(MethodResolveContext methodResolveContext) {
        this.methodResolveContext = methodResolveContext;
    }

    public ComponentMeta getComponentMeta(){
        return componentMeta;
    }

    public Class getComponentClass(){
        return componentMeta.getComponentClass();
    }
}
